Skip to content

feat(developer): kmc convert typesafety 😎#15860

Open
SabineSIL wants to merge 18 commits into
feat/developer/kmc-convertfrom
feat/developer/kmc-convert-Typesafety
Open

feat(developer): kmc convert typesafety 😎#15860
SabineSIL wants to merge 18 commits into
feat/developer/kmc-convertfrom
feat/developer/kmc-convert-Typesafety

Conversation

@SabineSIL
Copy link
Copy Markdown
Contributor

@SabineSIL SabineSIL commented Apr 20, 2026

In several places in kmc-convert: keylayout->kmn we did not care about return type null or undefined for function return values or parameters. Therefore we got several warnings about possible return types not being assignable to symbols.

Even though they did not affect the code running correctly they have been specified more clearly in this PR.

@keymanapp-test-bot skip

see also:

@keymanapp-test-bot
Copy link
Copy Markdown

keymanapp-test-bot Bot commented Apr 20, 2026

User Test Results

Test specification and instructions

User tests are not required

Test Artifacts

  • Developer
    • Keyman Developer - build : all tests passed (no artifacts on BuildLevel "build")
    • Compiler Regression Tests - build : all tests passed (no artifacts on BuildLevel "build")
    • Keyman Developer (old PRs) - build : all tests passed (no artifacts on BuildLevel "build")
    • kmcomp.zip - build : all tests passed (no artifacts on BuildLevel "build")
    • kmcomp.zip (old PRs) - build : all tests passed (no artifacts on BuildLevel "build")
  • Keyboards
    • Test Keyboards - build : all tests passed (no artifacts on BuildLevel "build")

@keymanapp-test-bot keymanapp-test-bot Bot changed the title feat/developer/kmc convert typesafety feat/developer/kmc convert typesafety 😎 Apr 20, 2026
@SabineSIL SabineSIL changed the title feat/developer/kmc convert typesafety 😎 feat(developer):kmc convert typesafety 😎 Apr 21, 2026
@SabineSIL SabineSIL changed the title feat(developer):kmc convert typesafety 😎 feat(developer): kmc convert typesafety 😎 Apr 21, 2026
@SabineSIL SabineSIL marked this pull request as ready for review April 21, 2026 15:44
@keyman-server keyman-server modified the milestones: A19S27, A19S28 Apr 24, 2026
@keyman-server keyman-server modified the milestones: A19S28, A19S29 May 11, 2026
@SabineSIL SabineSIL moved this from Todo to Code Review in Keyman May 18, 2026
SabineSIL and others added 5 commits May 19, 2026 09:57
…vert-Typesafety

# Conflicts:
#	developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts
#	developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts
#	developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts
#	developer/src/kmc-convert/test/kmn-file-writer.tests.ts
Copy link
Copy Markdown
Contributor

@ermshiperete ermshiperete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good despite the many comments.

Comment on lines +21 to +23
* @brief helper function to check if a specific keyMap index exists in a keyMapSet
* neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to make this full sentences. If this gets processed by a tool the three lines might get put in the same paragraph removing the line breaks, which then would make it hard to understand.

Suggested change
* @brief helper function to check if a specific keyMap index exists in a keyMapSet
* neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>
* @brief Helper function to check if a specific keyMap index exists in a keyMapSet.
* This is neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>.

Comment on lines +40 to +42
* @brief helper function to check if a specific keyMapSelect index exists in a modifierMap
* neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief helper function to check if a specific keyMapSelect index exists in a modifierMap
* neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>
* @brief Helper function to check if a specific keyMapSelect index exists in a modifierMap.
* This is neccessary because the amount of <keyMap index> must correspond to
* the amount of <keyMapSelect mapIndex>.

behavior: string;
modifier?: string;
outchar?: string;
outchar?: string | undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be necessary. outchar? makes the property optional which implies undefined. So if you write outchar?: string; the type of outchar is implicitly string|undefined.

Comment on lines +318 to +320
// in case writeCharacterOrUnicode() returns null, the fallback is empty strings for characterMessage.character
// and characterMessage.message. Then versionOutputCharacter could be "" and would be written into the kmn file
// as ... > '', producing an invalid kmn rule.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment. It describes what the code does and that this could result in an invalid kmn rule. Wouldn't we want to do something so that we don't get invalid kmn rules? (in which case I'd expect a TODO or something).

Or does the comment try to explain why we have the if ((outputCharacter !== undefined) && (outputCharacter !== "")) check? Then it should mention outputCharacter which could lead to writeCharacterOrUnicode returning false...

(Additional occurrences of the same code comment above)

* null in case of an empty string or null or undefined input
*/
public writeCharacterOrUnicode(ctr: string, msg: string = ""): MessageCharacter {
public writeCharacterOrUnicode(ctr: string, msg: string = ""): MessageCharacter | null {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if writeCharacterToUnicode would always returns MessageCharacter. In the error case it could return { character: '', message: '' }. IMO this would simplify things quite a bit.

].forEach(function (values) {
it(('should convert "' + values[0] + '"').padEnd(25, " ") + 'to "' + values[2] + '"', async function () {
const result = sutW.writeCharacterOrUnicode(values[0] as string, values[1] as string);
if (result) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to split this into two tests: one set that tests the cases where we get a result, and the other for the cases that return null.

});
it(('null should create empty string '), async function () {
const result1 = sutW.writeDataRules(null);
assert.isTrue(result1 === '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use

Suggested change
assert.isTrue(result1 === '');
assert.equal(result1, '');

it('run() should throw on unavailable input file name and null output file name', async function () {
const inputFilename = makePathToFixture('../data/Unavailable.keylayout');
const result = sut.run(inputFilename, null);
const result = sut.run(inputFilename, undefined);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since run is async, we'll have to await it:

Suggested change
const result = sut.run(inputFilename, undefined);
const result = await sut.run(inputFilename, undefined);

this.callbacks.reportMessage(ConverterMessages.Error_UnsupportedCharactersDetected({
keymapIndex: jsonObj.keyboard.keyMapSet[0].keyMap[i]['index'],
output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'] ?? '',
output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'] ?? '' as string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as string is unnecessary since it's already a string.

Suggested change
output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'] ?? '' as string,
output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'] ?? '',


// use of Unicode Character vs Unicode Codepoint;
// If it`s a ctrl character we print out the Unicode Codepoint else we print out the Unicode Character
let versionOutputCharacter;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to initialize this to the empty string, otherwise we could theoretically end up with an uninitialized variable if the if in line 163 evaluates to false.

Suggested change
let versionOutputCharacter = '';

@keyman-server keyman-server modified the milestones: A19S29, A19S30 May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Code Review

Development

Successfully merging this pull request may close these issues.

3 participants